#ifndef SCRIPTMANAGER_H
#define SCRIPTMANAGER_H

/* Copyright (C) 2011 Riccardo Marson

   This file is part of OpenPool, an open-source game of pool.

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   RICCARDO MARSON BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
   AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

  See Readme.rtf for further information.

*/

#include "Rapidxml/rapidxml.hpp"

#include "AS/angelscript.h"
#include "as_addon/scriptbuilder.h"

#include <list>
#include <map>
#include <string>


/*
Script manager
*/
class CScriptManager
{
public:

    CScriptManager();
    ~CScriptManager();

    static CScriptManager* getSingleton();

    // initialize the scripting engine, callback, global functions and variables used across
    // scripts.
    void init();

    // loads and builds all the modules / scripts and register all the object types that the scripts
    // need to be aware of.
    bool configure();

    //proxy function for mScriptEngine->RegisterGlobalProperty(const char* declaration, void* pointer)
    bool registerGlobalProperty(const char* declaration, void *pointer);

    int GetASTypeID(std::string decl);

    // TODO: add a "create object" method that returns a asIScriptObject* with its relative function IDs.
    // every host-side object will be responsible for storing a reference to its script counterpart.
    // ==> see <anglescript>/samples/game for reference.

    // Directly execute a pre-built function.
    // The first overload fetches the function from the module name
    // and function declaration (safest if there are overloads), while the second overload assumes
    // that a pointer to the correct function has been stored after compilation.
    bool executeFunction(std::string moduleName, std::string funcDecl);
    bool executeFunction(std::string moduleName, std::string funcDecl, int argNumber, void **args);
    bool executeFunction(asIScriptFunction* func);

    // Queue a function for later execution. Similarly to the event system, the main loop controls
    // when the queue is actually processed.
    // As before, the first overload fetches the right function from its module name/declaration,
    // while the second assumes the correct function is passed in.
    bool queueFunctionExecution(std::string moduleName, std::string funcDecl);
    bool queueFunctionExecution(asIScriptFunction* func);

    // Batch execute queued functions.
    void processQueuedFunctions();

private:

    void ProcessXmlNode(rapidxml::xml_node<> *node);

    bool beginModule(std::string moduleName);
    bool addSource(std::string srcName, std::string sectionName);
    bool buildModule();

    // Executes a function call, printing information on failure, if any.
    bool execute(asIScriptContext* context);

    // utility function used by the script engine to communicate issues to the host application.
    // registered as a global function.
    void ScriptMessageCallback(const asSMessageInfo *scriptMsg, void *param);

    // gets the first available context from the beginning of the stack, and prepares
    // its memory stack for execution of (script) function "func".
    asIScriptContext* getAvailableContext(asIScriptFunction* func);
    // frees a context currently in use and returns it to the end of the stack
    void freeActiveContext(asIScriptContext* context);

    // swap the current active and inactive process queues;
    inline void swapQueues();
    // determines which is the inactive queue (where newly queued functions are stored)
    inline unsigned short whichQueue();

    static CScriptManager* uniqueInstance;

    asIScriptEngine *mScriptEngine;

    // helper class from as add-ons
    CScriptBuilder mScriptBuilder;

    // holds the script source file names divided by module and keyed by module name.
    typedef std::list< std::pair < std::string, std::string > > ScriptModuleSource;
    typedef std::list< std::pair < std::string, std::string > >::iterator ScriptModuleSourceIt;
    typedef std::map< std::string, ScriptModuleSource > ScriptModules;
    typedef std::map< std::string, ScriptModuleSource >::iterator ScriptModuleIt;

    ScriptModules mModuleList;

    // keep a record of the internal type IDs that the angelscript engine assignes to each
    // type registered by the host application
    std::map<std::string, int> mRegisteredTypeIDs;

    /*
     * Define a shared pool of contexts. Context creation is an heavy-weight operation
     * and should be done as few times as possible. Since global variables are stored by module
     * and not by context, contexts can be shared between different runs and modules.
     * The pool is implemented as a stack of free contexts, using std::list<>.
     * When a context is needed, it's popped out of the stack (effectively diminishing the stack size).
     * When it's no longer needed, its memory stack is cleared and it's pushed back into the stack.
     */
    typedef std::list<asIScriptContext*> ContextStack;

    ContextStack mContextStack;

    // List of functions awaiting execution. Newly queued functions are stored in a back (inactive) queue
    // for added safety. Once the active queue is empty, the two are swapped.
    typedef std::list<asIScriptFunction*> ExecQueue;

    ExecQueue mPendingFunctions[2];

    unsigned short mActiveQueue; // the index of the queue that will be processed in the next loop
    float mMaxProcessingTime; // maximum time allowed for queue processing in each loop
};


inline unsigned short CScriptManager::whichQueue()
{
	// store in the inactive queue
	unsigned short backQueue;

	if (mActiveQueue == 0)
		backQueue = 1;
	else
		backQueue = 0;

	return backQueue;
}

inline void CScriptManager::swapQueues()
{
	if (mActiveQueue == 0)
		mActiveQueue = 1;
	else
		mActiveQueue = 0;
}


#endif // SCRIPTMANAGER_H
